package arch.sm213.machine.student;

import machine.AbstractMainMemory;
import util.UnsignedByte;

/**
 * Main Memory of Simple CPU.
 *
 * Provides an abstraction of main memory (DRAM).
 */

public class MainMemory extends AbstractMainMemory {
	private byte [] mem;      // Memory is an array of bytes

	/**
	 * Allocate memory.
	 * @param byteCapacity size of memory in bytes.
	 */
	public MainMemory (int byteCapacity) {

		// Need to implement the method
		mem = new byte[byteCapacity];
	}

	/**
	 * Determine whether an address is aligned to specified length.
	 * @param address memory address.
	 * @param length byte length.
	 * @return true iff address is aligned to length.
	 */
	@Override protected boolean isAccessAligned (int address, int length) {

		// Need to implement this method and change the returned value
		if (address % length == 0)
			return true;
		return false;
	}

	/**
	 * Convert an sequence of four bytes into a Big Endian integer.
	 * @param byteAtAddrPlus0 value of byte with lowest memory address (base address).
	 * @param byteAtAddrPlus1 value of byte at base address plus 1.
	 * @param byteAtAddrPlus2 value of byte at base address plus 2.
	 * @param byteAtAddrPlus3 value of byte at base address plus 3 (highest memory address).
	 * @return Big Endian integer formed by these four bytes.
	 */
	@Override public int bytesToInteger (UnsignedByte byteAtAddrPlus0, UnsignedByte byteAtAddrPlus1, UnsignedByte byteAtAddrPlus2, UnsignedByte byteAtAddrPlus3) {

		// Need to implement this method and change the returned value
		int i = 0;
		i = (int) (byteAtAddrPlus0.value() << 24 | byteAtAddrPlus1.value() << 16 | byteAtAddrPlus2.value() << 8 | byteAtAddrPlus3.value());
		return i;
	}

	/**
	 * Convert a Big Endian integer into an array of 4 bytes organized by memory address.
	 * @param  i an Big Endian integer.
	 * @return an array of UnsignedByte where [0] is value of low-address byte of the number etc.
	 */
	@Override public UnsignedByte[] integerToBytes (int i) {

		//Need to implement this method and change the returned value
		UnsignedByte[] bytes = new UnsignedByte[4];
		byte b = (byte) i;
		for (int j = 0; j < 4; j++) {
			bytes[j] = new UnsignedByte(b >> j - 3);
		}
		return bytes;
	}

	/**
	 * Fetch a sequence of bytes from memory.
	 * @param address address of the first byte to fetch.
	 * @param length  number of bytes to fetch.
	 * @return an array of UnsignedByte where [0] is memory value at address, [1] is memory value at address+1 etc.
	 */
	@Override protected UnsignedByte[] get (int address, int length) throws InvalidAddressException {

		// Need to implement the method and change the return value
		UnsignedByte[] temp = new UnsignedByte[4];
		boolean isOutOfBounds = false;
		for (int i = 0; i < length; i++)
			if (i > this.length())
				isOutOfBounds = true;
		if (isOutOfBounds == true)
			throw new InvalidAddressException();
		int i = 0;
		while (i < length)
			temp[i] = new UnsignedByte(mem[address + i]);
		return temp;
	}

	/**
	 * Store a sequence of bytes into memory.
	 * @param  address                  address of the first byte in memory to receive the specified value.
	 * @param  value                    an array of UnsignedByte values to store in memory at the specified address.
	 * @throws InvalidAddressException  if any address in the range address to address+value.length-1 is invalid.
	 */
	@Override protected void set (int address, UnsignedByte[] value) throws InvalidAddressException {

		// Need to implement the method
		boolean isOutOfBounds = false;
		for (int i = 0; i < value.length; i++)
			if (i > this.length())
				isOutOfBounds = true;
		if (isOutOfBounds == true)
			throw new InvalidAddressException();
		int i = 0;
		while (i < value.length) {
			mem[address + i] = (byte) value[i].value();
			i++;
		}
	}

	/**
	 * Determine the size of memory.
	 * @return the number of bytes allocated to this memory.
	 */
	@Override public int length () {

		// Need to implement the method and change the return value
		int a = 0;
		for (int i = 0; i < mem.length; i++)
			a++;
		return a;
	}
}
